/**
* Copyright (c) 2012-2013 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. 
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners. 
* See LICENSE.TXT for license information.
*/

package com.nokia.example.paint.views;

import com.nokia.example.paint.Main;
import com.nokia.example.paint.helpers.DrawableLine;
import com.nokia.example.paint.touch.MultiTouchListener;
import com.nokia.example.paint.touch.MultiTouch;
import com.nokia.example.paint.views.components.ColorChangeListener;
import com.nokia.example.paint.views.components.ColorPicker;
import com.nokia.example.paint.views.components.ColorTool;
import com.nokia.example.paint.views.components.FileSaveDialog;
import com.nokia.example.paint.views.components.Toolbar;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;

/*
 * Implements the draw area.
 */
public class DrawArea
    extends GameCanvas
    implements ColorChangeListener, MultiTouchListener {

    public static final int SMALL_BRUSH = 4;
    public static final int MEDIUM_BRUSH = 6;
    public static final int LARGE_BRUSH = 8;
    public static final int XL_BRUSH = 10;
    private final int TOUCH_POINTS;
    private Graphics g;
    private Timer mTimer;
    private int[] previousX;
    private int[] previousY;
    private int brushWidth = SMALL_BRUSH;
    private Image drawing;
    private Graphics dg;
    private Toolbar toolbar;
    private ColorPicker colorpicker;
    private FileSaveDialog savedialog;
    private boolean drawingAllowed = true;
    private boolean drawingChanged = false;
    private boolean renderDrawing = false;
    private Vector[] buffers;

    public DrawArea() {
        super(false);
        setFullScreenMode(true);
        int width = getWidth();
        int height = getHeight();
        drawing = Image.createImage(width, height - Toolbar.HEIGHT);
        dg = drawing.getGraphics();
        toolbar = new Toolbar(width);
        ColorTool colortool = new ColorTool();
        colortool.colorChanged(0x000000);  // initial color
        toolbar.addTool(colortool, 1);
        colorpicker = new ColorPicker(width);
        colorpicker.addListener(this);
        colorpicker.addListener(colortool);
        savedialog = new FileSaveDialog(this);

        MultiTouch.setTouchListener(this);
        TOUCH_POINTS = MultiTouch.getMaxPointers();
        buffers = new Vector[TOUCH_POINTS];
        previousX = new int[TOUCH_POINTS];
        previousY = new int[TOUCH_POINTS];

        for (int i = 0; i < TOUCH_POINTS; i++) {
            buffers[i] = new Vector();
            previousX[i] = -1;
            previousY[i] = -1;
        }
        
        Command backCommand = new Command("Back", Command.BACK, 0);
        addCommand(backCommand);
        setCommandListener(new CommandListener() {
            public void commandAction(Command c, Displayable d) {
                if (c.getCommandType() == Command.BACK) {
                    Main main = Main.getInstance();
                    if (main.getDrawArea().isDrawingChanged()) {
                        main.showExitPrompt();
                    } else {
                        main.exit();
                    }
                }
            }
        
        });
    }

    /**
     * Called when showing canvas.
     * @see javax.microedition.lcdui.Canvas#showNotify()
     */
    protected void showNotify() {
        mTimer = new Timer();
        g = getGraphics();
        g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP);
        // Periodically update draw area graphics.
        TimerTask ui = new TimerTask() {

            public void run() {
                render();
                flushGraphics();
            }
        };
        mTimer.schedule(ui, 0, 60);
    }

    /**
     * Called when hiding the canvas.
     * @see javax.microedition.lcdui.Canvas#hideNotify()
     */
    protected void hideNotify() {
        mTimer.cancel();
    }

    protected void render() {
        g.setClip(0, 0, getWidth(), getHeight());
        toolbar.render(g);
        if (renderDrawing) {
            g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP);
            renderDrawing = false;
        }
        else {
            if (drawingAllowed) {
                boolean changed = false;
                for (int i = 0; i < TOUCH_POINTS; i++) {
                    /* Push all the touch events to a buffer, which is 
                     * read every 30 ms. If touch events were read immediately,
                     * events would be lost while drawing the line.
                     */ 
                    Vector buffer = buffers[i];
                    if (!buffer.isEmpty()) {
                        renderLines(buffer);
                        changed = true;
                    }
                }
                if (changed) {
                    g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP);
                }
            }
            else {
                g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP);
            }
            colorpicker.render(g);
            savedialog.render(g);
        }
    }

    /**
     * Line drawing is implemented by drawing overlapping filled circles along the drag path. 
     * (The diameter of the circles is determined by the selected brush size.) 
     * This is achieved by dividing the line between the drag events into small segments 
     * with a length of 1/3 of the brush width. The 1/3 length presents a good trade-off for not 
     * generating too much unnecessary drawing load while still producing a decent line.
     * @param buffer holding the latest touch events.
     */
    private void renderLines(Vector buffer) {
        int l = buffer.size();
        DrawableLine[] currentLines = new DrawableLine[buffer.size()];
        for (int i = 0; i < l; i++) {
            currentLines[i] = (DrawableLine) buffer.elementAt(i);
        }

        int s = currentLines.length;
        float divider = brushWidth / 3;
        for (int j = 0; j < s; j++) {
            DrawableLine line = currentLines[j];

            buffer.removeElement(line);
            int x1 = line.startX;
            int y1 = line.startY;
            int x2 = line.endX;
            int y2 = line.endY;
            // Draw a line between two drag events.
            if (x1 > 0 && y1 > 0 && x2 > 0 && y2 > 0) {
                float d = calcDistance(x1, y1, x2, y2);
                if (d > divider) {
                    int steps = (int) (d / divider + 0.5);
                    float dx = (x2 - x1) / (float) steps;
                    float dy = (y2 - y1) / (float) steps;
                    for (int i = 0; i < steps; i++) {
                        int x = (int) (x1 + dx * i + 0.5);
                        int y = (int) (y1 + dy * i + 0.5);
                        dg.fillArc(x, y - Toolbar.HEIGHT, brushWidth, brushWidth, 0, 360);
                    }
                }
                dg.fillArc((int) x2, (int) y2 - Toolbar.HEIGHT, brushWidth, brushWidth, 0, 360);
            }
        }
    }

    private float calcDistance(float x1, float y1, float x2, float y2) {
        return (float) (Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)));
    }

    /**
     * Handle pointer press events.
     * @see javax.microedition.lcdui.Canvas#pointerPressed(int, int)
     * @param x coordinate of press
     * @param y coordinate of press
     */
    public void pointerPressed(int x, int y) {
        if (TOUCH_POINTS == 1) { // Prevent redundant calls
            pointerPressed(x, y, 0);
        }
    }

    /**
     * Handle pointer drag events.
     * @see javax.microedition.lcdui.Canvas#pointerDragged(int, int)
     * @param x coordinate
     * @param y coordinate
     */
    public void pointerDragged(int x, int y) {
        if (TOUCH_POINTS == 1) {
            pointerDragged(x, y, 0);
        }
    }

    /**
     * Handle pointer release events.
     * @see javax.microedition.lcdui.Canvas#pointerReleased(int, int)
     * @param x coordinate
     * @param y coordinate
     */
    public void pointerReleased(int x, int y) {
        if (TOUCH_POINTS == 1) {
            pointerReleased(x, y, 0);
            // Signifies there are unsaved changes; the exit dialog
            // will be called when user attempts to exit.
            drawingChanged = true;
        }
    }

    /**
     * Handle multitouch events, which are delivered through MultiTouch wrapper.
     * @see com.nokia.example.paint.touch.MultiTouchListener#pointerPressed(int, int, int)
     * @param x coordinate of press
     * @param y coordinate of press
     * @param id id of pointer
     */
    public void pointerPressed(int x, int y, int id) {
        if (drawingAllowed) {
            if (y > Toolbar.HEIGHT) {
                // Add events to buffer
                buffers[id].addElement(new DrawableLine(x, y, x, y));
                previousX[id] = x;
                previousY[id] = y;
            }
            else {
                toolbar.pointerPressed(x, y);
            }
        }
        else if (savedialog.isVisible()) {
            savedialog.pointerPressed(x, y);
        }
    }

    /**
     * Handle multitouch events, which are delivered through MultiTouch wrapper.
     * @see com.nokia.example.paint.touch.MultiTouchListener#pointerDragged(int, int, int)
     * @param x coordinate of drag
     * @param y coordinate of drag
     * @param id id of pointer
     */
    public void pointerDragged(int x, int y, int id) {
        if (!drawingAllowed && colorpicker.isVisible()) {
            colorpicker.dragged(x, y);
        }
        else if (drawingAllowed && y > Toolbar.HEIGHT) {
            // Add events to buffer.
            buffers[id].addElement(new DrawableLine(previousX[id], previousY[id], x, y));
            previousX[id] = x;
            previousY[id] = y;
        }
    }

    /**
     * Handle multitouch events, which are delivered through MultiTouch wrapper.
     * @see com.nokia.example.paint.touch.MultiTouchListener#pointerDragged(int, int, int)
     * @param x coordinate of release
     * @param y coordinate of release
     * @param id id of pointer
     */
    public void pointerReleased(int x, int y, int id) {
        if (!drawingAllowed) {
            if (colorpicker.isVisible()) {
                colorpicker.pressed(x, y);
            }
            else if (savedialog.isVisible()) {
                savedialog.pointerUnpressed(x, y);
            }
        }
        else {
            previousX[id] = -1;
            previousY[id] = -1;
            toolbar.pointerUnpressed(x, y);
            drawingChanged = true;
        }
    }

    public Image getDrawing() {
        return drawing;
    }

    public void setBrush(final int brushsize) {
        brushWidth = brushsize;
    }

    public void showColorPicker() {
        drawingAllowed = false;
        colorpicker.show();
    }

    public void hideColorPicker() {
        colorpicker.hide();
        g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.TOP | Graphics.LEFT);
        drawingAllowed = true;
    }

    public void colorChanged(int brushColor) {
        dg.setColor(brushColor);
    }

    public void clearDrawing() {
        int oldcolor = dg.getColor();
        dg.setColor(0xffffff);
        dg.fillRect(0, 0, drawing.getWidth(), drawing.getHeight());
        dg.setColor(oldcolor);
        renderDrawing = true;
        drawingChanged = false;
    }

    public void showSaveDialog(boolean quitAfterSave) {
        drawingAllowed = false;
        savedialog.show(quitAfterSave);
    }

    public void allowDrawing(boolean value) {
        if (value) {
            renderDrawing = true;
        }
        drawingAllowed = value;
    }

    public boolean isDrawingChanged() {
        return drawingChanged;
    }

    public void setDrawingChanged(boolean changed) {
        drawingChanged = changed;
    }
}
